// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include <zencore/zencore.h>

#include <zencore/compositebuffer.h>
#include <zencore/enumflags.h>
#include <zencore/iobuffer.h>

#include <filesystem>
#include <functional>

namespace zen {

class CbObject;

/**
 * Probably the most basic file abstraction in the universe
 *
 * One thing of note is that there is no notion of a "current file position"
 * in this API -- all reads and writes are done from explicit offsets in
 * the file. This avoids concurrency issues which can occur otherwise.
 *
 */

class BasicFile
{
public:
	BasicFile() = default;
	~BasicFile();

	BasicFile(const BasicFile&) = delete;
	BasicFile& operator=(const BasicFile&) = delete;

	enum class Mode : uint32_t
	{
		kRead	  = 0,	// Opens a existing file for read only
		kWrite	  = 1,	// Opens (or creates) a file for read and write
		kTruncate = 2,	// Opens (or creates) a file for read and write and sets the size to zero
		kDelete	  = 3,	// Opens (or creates) a file for read and write allowing .DeleteFile file disposition to be set
		kTruncateDelete =
			4,	// Opens (or creates) a file for read and write and sets the size to zero allowing .DeleteFile file disposition to be set
		kModeMask	   = 0x0007,
		kPreventDelete = 0x1000'0000,  // Do not open with delete sharing mode (prevent other processes from deleting file while open)
		kPreventWrite  = 0x2000'0000,  // Do not open with write sharing mode (prevent other processes from writing to file while open)
	};

	void	 Open(const std::filesystem::path& FileName, Mode Mode);
	void	 Open(const std::filesystem::path& FileName, Mode Mode, std::error_code& Ec);
	void	 Open(const std::filesystem::path& FileName, Mode Mode, std::function<bool(std::error_code& Ec)>&& RetryCallback);
	void	 Close();
	void	 Read(void* Data, uint64_t Size, uint64_t FileOffset);
	IoBuffer ReadRange(uint64_t FileOffset, uint64_t ByteCount);
	void	 StreamFile(std::function<void(const void* Data, uint64_t Size)>&& ChunkFun);
	void	 StreamByteRange(uint64_t FileOffset, uint64_t Size, std::function<void(const void* Data, uint64_t Size)>&& ChunkFun);
	void	 Write(MemoryView Data, uint64_t FileOffset);
	void	 Write(MemoryView Data, uint64_t FileOffset, std::error_code& Ec);
	uint64_t Write(CompositeBuffer Data, uint64_t FileOffset, std::error_code& Ec);
	void	 Write(const void* Data, uint64_t Size, uint64_t FileOffset);
	void	 Write(const void* Data, uint64_t Size, uint64_t FileOffset, std::error_code& Ec);
	void	 Flush();
	[[nodiscard]] uint64_t FileSize();
	[[nodiscard]] uint64_t FileSize(std::error_code& Ec);
	void				   SetFileSize(uint64_t FileSize);
	IoBuffer			   ReadAll();
	void				   WriteAll(IoBuffer Data, std::error_code& Ec);
	void				   Attach(void* Handle);
	void*				   Detach();

	inline void* Handle() { return m_FileHandle; }
	bool		 IsOpen() const { return m_FileHandle != nullptr; }

protected:
	void* m_FileHandle = nullptr;  // This is either null or valid
private:
};

ENUM_CLASS_FLAGS(BasicFile::Mode);

/**
 * Simple abstraction for a temporary file
 *
 * Works like a regular BasicFile but implements a simple mechanism to allow creating
 * a temporary file for writing in a directory which may later be moved atomically
 * into the intended location after it has been fully written to.
 *
 */

class TemporaryFile : public BasicFile
{
public:
	TemporaryFile() = default;
	~TemporaryFile();

	TemporaryFile(const TemporaryFile&) = delete;
	TemporaryFile& operator=(const TemporaryFile&) = delete;

	void						 Close();
	void						 CreateTemporary(std::filesystem::path TempDirName, std::error_code& Ec);
	void						 MoveTemporaryIntoPlace(std::filesystem::path FinalFileName, std::error_code& Ec);
	const std::filesystem::path& GetPath() const { return m_TempPath; }

	static void SafeWriteFile(std::filesystem::path Path, MemoryView Data);

private:
	std::filesystem::path m_TempPath;

	using BasicFile::Open;
};

/** Lock file abstraction

 */

class LockFile : protected BasicFile
{
public:
	LockFile();
	~LockFile();

	void Create(std::filesystem::path FileName, CbObject Payload, std::error_code& Ec);
	void Update(CbObject Payload, std::error_code& Ec);

private:
};

/** Adds a layer of buffered reading to a BasicFile

This class is not intended for concurrent access, it is not thread safe.

*/

class BasicFileBuffer
{
public:
	BasicFileBuffer(BasicFile& Base, uint64_t BufferSize);
	~BasicFileBuffer();

	void	   Read(void* Data, uint64_t Size, uint64_t FileOffset);
	MemoryView MakeView(uint64_t Size, uint64_t FileOffset);

	template<typename T>
	const T* MakeView(uint64_t FileOffset)
	{
		MemoryView View = MakeView(sizeof(T), FileOffset);
		return reinterpret_cast<const T*>(View.GetData());
	}

private:
	BasicFile&	   m_Base;
	uint8_t*	   m_Buffer;
	const uint64_t m_BufferSize;
	uint64_t	   m_Size;
	uint64_t	   m_BufferStart;
	uint64_t	   m_BufferEnd;
};

/** Adds a layer of buffered writing to a BasicFile

This class is not intended for concurrent access, it is not thread safe.

*/

class BasicFileWriter
{
public:
	BasicFileWriter(BasicFile& Base, uint64_t BufferSize);
	~BasicFileWriter();

	void Write(const void* Data, uint64_t Size, uint64_t FileOffset);
	void Flush();

private:
	BasicFile&	   m_Base;
	uint8_t*	   m_Buffer;
	const uint64_t m_BufferSize;
	uint64_t	   m_BufferStart;
	uint64_t	   m_BufferEnd;
};

ZENCORE_API void basicfile_forcelink();

}  // namespace zen
